package com.dynamic.mybatis.core.metadata;


import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.dynamic.mybatis.core.intercept.DynamicMappedStatementHandler;
import com.dynamic.mybatis.core.utils.StringUtils;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.parsing.XNode;

import java.util.*;

public class DynamicConfiguration extends MybatisConfiguration {
    private static final Log logger = LogFactory.getLog(DynamicConfiguration.class);
    protected final Map<String, DynamicMappedStatement> dynamicMappedStatements = new StrictMap<DynamicMappedStatement>("Dynamic Mapped Statement Attributes collection").conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.toString() + " and " + targetValue.toString());
    public final String DYNAMIC_RESOURCE = "com.dynamic.mybatis.DynamicMappedStatement";

    Set<DynamicMappedStatementHandler> mappedStatementHandlers = new HashSet<>();
    Set<String> temporaryNamespaces = new HashSet<>();

    public void addTemporaryNamespace(String temporaryNamespace) {
        if (StringUtils.isBlank(temporaryNamespace)) {
            return;
        }
        this.temporaryNamespaces.add(temporaryNamespace);
    }

    public Collection values() {
        return this.dynamicMappedStatements.values();
    }

    public void cleanTemporaryNamespace(String temporaryNamespace) {
        if (dynamicMappedStatements.size() < 1) {
            return;
        }

        List<DynamicMappedStatement> dmsList = new ArrayList<>();
        Iterator<Map.Entry<String, DynamicMappedStatement>> dmsIterator = dynamicMappedStatements.entrySet().iterator();
        while (dmsIterator.hasNext()) {
            Map.Entry entry = dmsIterator.next();
            Object value = entry.getValue();
            if (value instanceof DynamicMappedStatement && ((DynamicMappedStatement) value).getNamespace().equals(temporaryNamespace)) {
                dmsList.add((DynamicMappedStatement) value);
            }
        }
        cleanDynamicMappedStatement(dmsList);
        //删除 sqlFragment
        Iterator<Map.Entry<String, XNode>> sqlFragmentIterator = sqlFragments.entrySet().iterator();
        while (sqlFragmentIterator.hasNext()) {
            Map.Entry entry = sqlFragmentIterator.next();
            Object value = entry.getValue();
            String key = (String) entry.getKey();
            if (!(value instanceof XNode) || !key.startsWith(temporaryNamespace)) {
                continue;
            }
            //删除 DynamicMappedStatement
            sqlFragmentIterator.remove();
        }
    }

    public void cleanDynamicMappedStatement(List<DynamicMappedStatement> dmsList) {
        if (CollectionUtils.isEmpty(dmsList)) {
            return;
        }
        dmsList.stream().forEach(dms -> {
            String key = dms.getId();
            //删除 DynamicMappedStatement
            dynamicMappedStatements.remove(key);
            MappedStatement ms = getMappedStatement(key);
            if (ms == null) {
                return;
            }
            //删除 MappedStatement
            super.mappedStatements.remove(key);
            //删除 ParameterMap
            super.parameterMaps.remove(ms.getParameterMap().getId());
            //删除 ResultMap
            if (ms.getResultMaps() != null) {
                for (ResultMap rm : ms.getResultMaps()) {
                    super.resultMaps.remove(rm.getId());
                }
            }
        });
    }


    public void addMappedStatementHandler(DynamicMappedStatementHandler mappedStatementHandler) {
        if (mappedStatementHandler == null) {
            return;
        }
        mappedStatementHandlers.add(mappedStatementHandler);
    }

    public void addMappedStatementHandler(Collection<DynamicMappedStatementHandler> mappedStatementHandlers) {
        if (mappedStatementHandlers == null || mappedStatementHandlers.size() < 1) {
            return;
        }
        for (DynamicMappedStatementHandler dmsi : mappedStatementHandlers) {
            addMappedStatementHandler(dmsi);
        }
    }

    public Set<DynamicMappedStatementHandler> mappedStatementHandlers() {
        return this.mappedStatementHandlers;
    }

    public DynamicMappedStatement getDynamicMappedStatement(String mappedStatementId) {
        return dynamicMappedStatements.containsKey(mappedStatementId) ? dynamicMappedStatements.get(mappedStatementId) : null;
    }

    public Map<String, DynamicMappedStatement> getDynamicMappedStatements() {
        return dynamicMappedStatements;
    }

    public Boolean hasMappedStatement(String id) {
        return mappedStatements.containsKey(id);
    }

    public Boolean hasDynamicMappedStatement(String id) {
        return dynamicMappedStatements.containsKey(id);
    }

    public boolean isResourceLoaded(String resource) {
        if (!DYNAMIC_RESOURCE.equals(resource)) {
            return loadedResources.contains(resource);
        }
        return false;
    }

    public void addDynamicMappedStatement(DynamicMappedStatement dms, boolean handler) {
        logger.debug("addDynamicMappedStatement: " + dms.getId());
        DynamicMappedStatement dmsNative = getDynamicMappedStatement(dms.getId());
        if (dmsNative != null) {
            if (StringUtils.isNotEmpty(dmsNative.getUid()) && !Objects.equals(dmsNative.getUid(), dms.getUid())) {
                throw new IllegalArgumentException(dmsNative.getId() + "[uid[" + dmsNative.getUid() + "]]" + " already contains value for " + dms.getId() + "[uid[" + dms.getUid() + "]]");
            } else {
                logger.warn("addDynamicMappedStatement  replace existed. " + dms.getId());
                cleanDynamicMappedStatement(Arrays.asList(dmsNative));
            }
        }
        DynamicMappedStatementHandler.OPERATE operate = null;
        if (handler) {
            operate = before(dms);
        }
        dynamicMappedStatements.put(dms.getId(), dms);
        if (handler) {
            after(operate, dms);
        }
        if (StringUtils.isBlank(dms.getUid())) {
            dms.setUid(UUID.randomUUID().toString());
        }
    }

    @Override
    public void addMappedStatement(MappedStatement ms) {
        logger.debug("addMappedStatement: " + ms.getId());
        if (mappedStatements.containsKey(ms.getId())) {
            logger.error("mapper[" + ms.getId() + "] is ignored, because it exists, maybe from xml file");
            mappedStatements.put(ms.getId(), ms);
        }
        super.addMappedStatement(ms);
    }

    @Override
    public void addParameterMap(ParameterMap pm) {
        logger.debug("addParameterMap: " + pm.getId());
        if (resultMaps.containsKey(pm.getId())) {
            logger.error("parameterMaps[" + pm.getId() + "] is ignored, because it exists, maybe from xml file");
        }
        super.addParameterMap(pm);
    }

    @Override
    public void addResultMap(ResultMap rm) {
        logger.debug("addResultMap: " + rm.getId());
        if (resultMaps.containsKey(rm.getId())) {
            logger.error("resultMap[" + rm.getId() + "] is ignored, because it exists, maybe from xml file");
        }
        super.addResultMap(rm);
    }

    public void addSqlFragments(Map<String, XNode> xNodes) {
        sqlFragments.putAll(xNodes);
    }

    public MappedStatement getMappedStatement(String id) {
        return hasMappedStatement(id) ? getMappedStatement(id, true) : null;
    }


    private DynamicMappedStatementHandler.OPERATE before(DynamicMappedStatement dms) {
        DynamicMappedStatementHandler.OPERATE operate = hasDynamicMappedStatement(dms.getId()) ? DynamicMappedStatementHandler.OPERATE.UPDATE : DynamicMappedStatementHandler.OPERATE.ADD;
        if (this.mappedStatementHandlers.size() > 0) {
            for (DynamicMappedStatementHandler msi : this.mappedStatementHandlers) {
                msi.before(operate, dms);
            }
        }
        return operate;
    }

    private void after(DynamicMappedStatementHandler.OPERATE operate, DynamicMappedStatement dms) {
        if (this.mappedStatementHandlers.size() > 0) {
            for (DynamicMappedStatementHandler msi : this.mappedStatementHandlers) {
                msi.after(operate, dms);
            }
        }
    }
}
